/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with 
*  or without modification, are permitted provided that the 
*  following conditions are met:
*  Redistributions of source code must retain the above 
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above 
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials 
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef BEPNeuralLayerH
#define BEPNeuralLayerH
#include <NeuralNetwork/Perceptron/NeuralLayer/NeuralLayer.h>
#include <NeuralNetwork/LearningAlgorithm/BackPropagation/BPNeuron.h>
#include <NeuralNetwork/LearningAlgorithm/BackPropagation/INeuralLayer.h>

namespace nn {

    namespace bp {

        template<class Var>
        class ANeuralLayer : public INeuralLayer<Var> {
        protected:
            typename std::map<unsigned int, bp::BPNeuron<Var> > _neurons;
            typename utils::SharedPtr<NeuralLayer<Var> > _neuralLayer;
            Var _varP;

        public:

            ANeuralLayer(const typename utils::SharedPtr<NeuralLayer<Var> >& neuralLayer, const Var& varP){
                if (neuralLayer == NULL) {
                    throw NNException("Wrong neuralLayer pointer", __FILE__, __LINE__);
                }

                _neuralLayer = neuralLayer;

                unsigned int neuronsNumber = _neuralLayer->getNeuronsNumber();
                for (unsigned int i = 0; i < neuronsNumber; i++) {
                    BPNeuron<Var> bepNeuron(_neuralLayer->getNeuron(i));
                    if (_neurons.insert( std::make_pair( i, bepNeuron )  ).second == false) {
                        throw NNException("List error in", __FILE__, __LINE__);
                    }
                }

                _varP = varP;
            }

            virtual bool calculateLayerWeights(void) {
                bool result = false;
                try {
                    typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                    for (curNeuron = _neurons.begin(); curNeuron != _neurons.end(); curNeuron++) {
                        typename utils::SharedPtr<Neuron<Var> > neuron = curNeuron->second.getNeuron();
                        unsigned int inputsNumber = neuron->getInputsNumber();
                        Var delta = curNeuron->second.getDelta();
                        for (unsigned int i = 0; i < inputsNumber; i++) {
                            Var input = neuron->getInput(i);
                            Var weight = neuron->getInputWeight(i);
                            Var newWeight = weight - _varP * input * delta;
                            neuron->setInputWeight(i, newWeight);
                        }

                        Var weight = neuron->getNeuronWeight();
                        Var newWeight = weight - _varP * delta;
                        neuron->setNeuronWeight(newWeight);
                        result = true;
                    }
                } catch (const NNException&) {
                    result = false;
                }

                return result;
            }

            const BPNeuron<Var>& getNeuron(unsigned int neuronId) const{
                typename std::map<unsigned int, BPNeuron<Var> >::const_iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("List error", __FILE__, __LINE__);
                }

                return curNeuron->second;
            }

            unsigned int getNeuronsNumber(void) {
                return _neurons.size();
            }

            Var getInput(unsigned int neuronId, unsigned int inputId){
                typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("Wrong neuronId", __FILE__, __LINE__);
                }

                return curNeuron->second.getNeuron()->getInput(inputId);
            }

            Var getInputWeight(unsigned int neuronId, unsigned int weightId){
                typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("Wrong neuronId", __FILE__, __LINE__);
                }

                return curNeuron->second.getNeuron()->getInputWeight(weightId);
            }

            Var getNeuronWeight(unsigned int neuronId) {
                typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("Wrong neuronId", __FILE__, __LINE__);
                }

                return curNeuron->second.getNeuron()->getNeuronWeight();
            }

            Var getDifferential(unsigned int neuronId){
                typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("Wrong neuronId", __FILE__, __LINE__);
                }

                return curNeuron->second.calculateDifferential();
            }

            Var getDelta(unsigned int neuronId){
                typename std::map<unsigned int, BPNeuron<Var> >::iterator curNeuron;
                curNeuron = _neurons.find(neuronId);
                if (curNeuron == _neurons.end()) {
                    throw NNException("Wrong neuronId", __FILE__, __LINE__);
                }

                return curNeuron->second.getDelta();
            }

            virtual ~ANeuralLayer() throw () {
            }
        };

    }

}

#endif
